﻿/*
 * The MIT License (MIT)
 * 
 * Copyright (c) 2012 Brno University of Technology - Faculty of Information Technology (http://www.fit.vutbr.cz)
 * Author(s):
 * Vladimir Vesely (mailto:ivesely@fit.vutbr.cz)
 * Martin Mares
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 
 * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 
 * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.Serialization;
using System.Net;

namespace PcapMerger
{

    #region PcapNgInterface
    /// <summary>
    /// Represents interface block in PCAPng file
    /// </summary>
    public class PcapNgInterface
    {

        /// <summary>
        /// Prints whole TCPD Packet Header inforamtion of one frame record
        /// </summary>
        public enum InterfaceOptions
        {
            OptEndofopt = 0,
            IfName = 2,
            IfDescription = 3,
            IfIPv4Addr = 4,
            IfIPv6Addr = 5,
            IfMaCaddr = 6,
            IfEuIaddr = 7,
            IfSpeed = 8,
            IfTsresol = 9,
            IfTzone = 10,
            IfFilter = 11,
            IfOs = 12,
            IfFcslen = 13,
            IfTsoffset = 14
        }

        public PcapNgInterface(UInt16 linkType, UInt16 id)
        {
            Addresses = new List<IPAddress>();
            Tsresol = 0;
            LinkType = linkType;
            Id = id;
            SetSnapLenByLinkType();
        }

        public PcapNgInterface(UInt32 snapLen, UInt16 linkType, UInt16 id) : this(linkType, id)
        {
            SnapLen = snapLen;
        }

        void SetSnapLenByLinkType()
        {
            SnapLen = 65535;
        }

        public UInt16 Id;
        public UInt32 SnapLen;
        public UInt16 LinkType;
        public String IfName;
        public String IfDescription;
        public List<IPAddress> Addresses;
        public byte[] MacAddress;
        public byte[] EUIaddr;
        public UInt64 Speed;
        public bool HasTsresol;
        public byte Tsresol;
        public UInt64 Tzone;
        public byte[] Filter;
        public String Os;
        public uint Fcslen;
        public UInt64 Tsoffset;
    }

    #endregion

    class PmCapturePcapNg : PmCaptureBase
    {

        #region PcapNgLinkTypes

        /// <summary>
        /// Pcap NG interface Link types
        /// </summary>
        public enum PcapNgLinkType
        {
            LinktypeNull = 0,
            LinktypeEthernet = 1,
            LinktypeExpEthernet = 2,
            LinktypeAX25 = 3,
            LinktypePronet = 4,
            LinktypeChaos = 5,
            LinktypeTokenRing = 6,
            LinktypeArcnet = 7,
            LinktypeSLIP = 8,
            LinktypePPP = 9,
            LinktypeFDDI = 10,
            LinktypePPPHDLC = 50,
            LinktypePPPEther = 51,
            LinktypeSymantecFirewall = 99,
            LinktypeATMRfc1483 = 100,
            LinktypeRaw = 101,
            LinktypeSLIPBsdos = 102,
            LinktypePPPBsdos = 103,
            LinktypeCHDLC = 104,
            LinktypeIEEE80211 = 105,
            LinktypeATMClip = 106,
            LinktypeFrelay = 107,
            LinktypeLoop = 108,
            LinktypeEnc = 109,
            LinktypeLane8023 = 110,
            LinktypeHippi = 111,
            LinktypeHDLC = 112,
            LinktypeLinuxSll = 113,
            LinktypeLTALK = 114,
            LinktypeEconet = 115,
            LinktypeIpfilter = 116,
            LinktypePFLOG = 117,
            LinktypeCiscoIos = 118,
            LinktypePrismHeader = 119,
            LinktypeAironetHeader = 120,
            LinktypeHhdlc = 121,
            LinktypeIPOverFC = 122,
            LinktypeSunatm = 123,
            LinktypeRio = 124,
            LinktypePciExp = 125,
            LinktypeAurora = 126,
            LinktypeIEEE80211Radio = 127,
            LinktypeTzsp = 128,
            LinktypeArcnetLinux = 129,
            LinktypeJuniperMlppp = 130,
            LinktypeJuniperMlfr = 131,
            LinktypeJuniperEs = 132,
            LinktypeJuniperGgsn = 133,
            LinktypeJuniperMfr = 134,
            LinktypeJuniperATM2 = 135,
            LinktypeJuniperServices = 136,
            LinktypeJuniperATM1 = 137,
            LinktypeAppleIPOverIEEE1394 = 138,
            LinktypeMTP2WithPhdr = 139,
            LinktypeMTP2 = 140,
            LinktypeMTP3 = 141,
            LinktypeSCCP = 142,
            LinktypeDOCSIS = 143,
            LinktypeLinuxIRDA = 144,
            LinktypeIbmSp = 145,
            LinktypeIbmSn = 146
        }

        #endregion

        #region exceptions
        [Serializable]
        public class PcapNgParsingErrorException : Exception
        {
            public PcapNgParsingErrorException(string message)
                : base(message)
            { }

            protected PcapNgParsingErrorException(SerializationInfo info, StreamingContext ctxt)
                : base(info, ctxt)
            { }
        }

        [Serializable]
        public class PcapNgWriteException : Exception
        {
            public PcapNgWriteException(string message)
                : base(message)
            { }

            protected PcapNgWriteException(SerializationInfo info, StreamingContext ctxt)
                : base(info, ctxt)
            { }
        }
        #endregion

        #region Constructors

        /// <summary>
        /// Basic constructor - generaly for creating output file
        /// </summary>
        public PmCapturePcapNg() :
            base(PmSupportedTypes.CaptureFileTypes.PCAPNextGen)
        {
            ifaces = new List<List<PcapNgInterface>>();
        }

        /// <summary>
        /// Load constructor - generaly for loading input from file
        /// (no need to call OpenFile after using this constructor)
        /// </summary>
        public PmCapturePcapNg(String filePath, BinaryReader fileReader) :
            base(filePath, fileReader, PmSupportedTypes.CaptureFileTypes.PCAPNextGen)
        {
            ifaces = new List<List<PcapNgInterface>>();
        }

        #endregion

        #region PcapNGfileFormatVariables

        List<List<PcapNgInterface>> ifaces; // packets interfaces

        const UInt16 MajorVersion = 0x01;
        const UInt16 MinorVersion = 0x00;

        DateTime unixDateTimeBase = new DateTime(1970, 01, 01, 0, 0, 0);

        #endregion

        #region IPMCaptureFileIOMethods

        /// <summary>
        /// Initialize FrameTables and appropriate FrameVectors from input file
        /// </summary>
        override public void CreateFrameTable()
        {
            try
            {
                ParseFile();
            }
            catch (Exception ex)
            {
                PmConsolePrinter.PrintError("Error>\n" + ex.Message);
            }

        }

        /// <summary>
        /// Store current included frames to file (type is specified by class instance)
        /// </summary>
        override public bool CreateOutput(String outputFile)
        {
            //throw new NotImplementedException("CreateOutput() not implemented !");
            return CreatePcapNgOutput(outputFile);
        }

        public override DateTime? FirstTimeStamp()
        {
            throw new NotImplementedException();
        }

        #endregion

        #region PcapNgtoSupprotedtypesConversionMethods

        private PmSupportedTypes.LinkTypes convertPcapNgLayer2ToCommonLayer2(PcapNgLinkType linkType)
        {
            switch (linkType)
            {
                case PcapNgLinkType.LinktypeEthernet: return PmSupportedTypes.LinkTypes.Ethernet;
                case PcapNgLinkType.LinktypeFDDI: return PmSupportedTypes.LinkTypes.FDDI;
                case PcapNgLinkType.LinktypeRaw: return PmSupportedTypes.LinkTypes.Raw;
                case PcapNgLinkType.LinktypeIEEE80211: return PmSupportedTypes.LinkTypes.IEEE80211;
                case PcapNgLinkType.LinktypeATMRfc1483: return PmSupportedTypes.LinkTypes.ATMRfc1483;
                default: return PmSupportedTypes.LinkTypes.Null;
            }
        }

        private PcapNgLinkType convertCommonLayer2ToPcapNgLayer2(PmSupportedTypes.LinkTypes linkType)
        {
            switch (linkType)
            {
                case PmSupportedTypes.LinkTypes.Ethernet: return PcapNgLinkType.LinktypeEthernet;
                case PmSupportedTypes.LinkTypes.FDDI: return PcapNgLinkType.LinktypeFDDI;
                case PmSupportedTypes.LinkTypes.Raw: return PcapNgLinkType.LinktypeRaw;
                case PmSupportedTypes.LinkTypes.IEEE80211: return PcapNgLinkType.LinktypeIEEE80211;
                case PmSupportedTypes.LinkTypes.ATMRfc1483: return PcapNgLinkType.LinktypeATMRfc1483;
                default: return PcapNgLinkType.LinktypeNull;
            }
        }

        #endregion

        #region PcapNgParsing

        #region ByteOrdeSwapMethods

        /// <summary>
        /// Swap 16-bit number byte order
        /// </summary>
        public static UInt16 Swap(UInt16 input)
        {
            return ((UInt16)(((0xFF00 & input) >> 8) | ((0x00FF & input) << 8)));
        }

        /// <summary>
        /// Swap 32-bit number byte order
        /// </summary>
        public static UInt32 Swap(UInt32 input)
        {
            return ((0xFF000000 & input) >> 24) | ((0x00FF0000 & input) >> 8) |
                   ((0x0000FF00 & input) << 8) | ((0x000000FF & input) << 24);
        }

        /// <summary>
        /// Swap 64-bit number byte order
        /// </summary>
        public static UInt64 Swap(UInt64 input)
        {
            return ((0x00000000000000FF) & (input >> 56) | (0x000000000000FF00) & (input >> 40)
                  | (0x0000000000FF0000) & (input >> 24) | (0x00000000FF000000) & (input >> 8)
                  | (0x000000FF00000000) & (input << 8) | (0x0000FF0000000000) & (input << 24)
                  | (0x00FF000000000000) & (input << 40) | (0xFF00000000000000) & (input << 56));
        }

        #endregion

        /// <summary>
        /// Pcap-ng file blocks types
        /// </summary>
        enum PcapNgBlockType
        {
            SectionHeader,
            InterfaceDescription,
            PacketBlock,
            SimplePacket,
            EnhancedPacket,
            InterfaceStatistics,
            NameResolution,
            Unknown
        }

        #region GeneralBlockParsing

        /// <summary>
        /// Read general block type value
        /// </summary>
        UInt32 GetGeneralBlockType(UInt32 offset, bool swapValue)
        {
            BinReader.BaseStream.Seek(offset, SeekOrigin.Begin);
            UInt32 blockType = BinReader.ReadUInt32();
            return swapValue ? Swap(blockType) : blockType;
        }

        /// <summary>
        /// Read general block length
        /// </summary>
        UInt32 GetGeneralBlockTotalLength(UInt32 offset, bool swapValue)
        {
            BinReader.BaseStream.Seek(offset + 4, SeekOrigin.Begin);
            UInt32 totalLength = BinReader.ReadUInt32();
            return swapValue ? Swap(totalLength) : totalLength;
        }

        /// <summary>
        /// Adjust block length to count new offset
        /// </summary>
        UInt32 AdjustLength(UInt32 blockLength)
        {
            switch (blockLength % 4)
            {
                case 0: return blockLength;
                case 1: return blockLength + 3;
                case 2: return blockLength + 2;
                case 3: return blockLength + 1;
                default:
                    return blockLength;
            }
        }

        /// <summary>
        /// Count padding length that had to be added to current data
        /// </summary>
        UInt32 CountPadding(UInt32 blockLength)
        {
            switch (blockLength % 4)
            {
                case 0: return 0;
                case 1: return 3;
                case 2: return 2;
                case 3: return 1;
                default:
                    return 0;
            }
        }

        /// <summary>
        /// Count next starting offset behind current block (at current offset)
        /// </summary>
        UInt32 GetNextGeneralBlokOffset(UInt32 offset, bool swapValue)
        {
            return offset + AdjustLength(GetGeneralBlockTotalLength(offset, swapValue));
        }

        /// <summary>
        /// Converts general block value included in file to enumaration type
        /// </summary>
        PcapNgBlockType RecognizeGeneralBlock(UInt32 offset, bool swapValue)
        {

            UInt32 blockType = GetGeneralBlockType(offset, swapValue);

            // PmConsolePrinter.PrintDebug("Block type\t: " + blockType);

            switch (blockType)
            {
                case 0x0A0D0D0A: return PcapNgBlockType.SectionHeader;
                case 0x00000001: return PcapNgBlockType.InterfaceDescription;
                case 0x00000002: return PcapNgBlockType.PacketBlock;
                case 0x00000003: return PcapNgBlockType.SimplePacket;
                case 0x00000005: return PcapNgBlockType.InterfaceStatistics;
                case 0x00000006: return PcapNgBlockType.EnhancedPacket;
                case 0x00000004: return PcapNgBlockType.NameResolution;
            }

            return PcapNgBlockType.Unknown;
        }

        #endregion

        #region ParseOptionBlock

        UInt16 GetOptionCode(UInt32 offset, bool swapValue)
        {
            BinReader.BaseStream.Seek(offset, SeekOrigin.Begin);
            UInt16 optionCode = BinReader.ReadUInt16();
            return swapValue ? Swap(optionCode) : optionCode;
        }

        UInt16 GetOptionLength(UInt32 offset, bool swapValue)
        {
            BinReader.BaseStream.Seek(offset + 2, SeekOrigin.Begin);
            UInt16 optionLength = BinReader.ReadUInt16();
            return swapValue ? Swap(optionLength) : optionLength;
        }

        UInt64 GetOptionUint64(UInt32 offset, bool swapValue)
        {
            BinReader.BaseStream.Seek(offset, SeekOrigin.Begin);
            UInt64 num = BinReader.ReadUInt64();
            return swapValue ? Swap(num) : num;
        }

        byte GetOptionByte(UInt32 offset)
        {
            BinReader.BaseStream.Seek(offset, SeekOrigin.Begin);
            return BinReader.ReadByte();
        }

        String GetOptionString(UInt32 offset, UInt32 length)
        {
            byte[] stringBuffer = new byte[length];
            BinReader.BaseStream.Seek(offset, SeekOrigin.Begin);
            BinReader.Read(stringBuffer, 0, (int)length);
            return Encoding.UTF8.GetString(stringBuffer, 0, (int)length);
        }

        byte[] GetOptionBytes(UInt32 offset, UInt32 length)
        {
            byte[] dataBuffer = new byte[length];
            BinReader.BaseStream.Seek(offset, SeekOrigin.Begin);
            BinReader.Read(dataBuffer, 0, (int)length);
            return dataBuffer;
        }

        #endregion

        #region SectionHeaderBlock

        UInt32 GetByteOrderMagic(UInt32 offset)
        {
            BinReader.BaseStream.Seek(offset + 8, SeekOrigin.Begin);
            return BinReader.ReadUInt32();
        }


        UInt32 GetVersion(UInt32 offset, bool swapValue)
        {
            BinReader.BaseStream.Seek(offset + 12, SeekOrigin.Begin);
            UInt16 versionMa = BinReader.ReadUInt16();
            UInt16 versionMi = BinReader.ReadUInt16();

            if (swapValue)
            {
                versionMa = Swap(versionMa);
                versionMi = Swap(versionMi);
            }

            return (((UInt32)versionMa) << 16) | versionMi;
        }




        void ParseSectionHeader(UInt32 currentOffset, out bool swapEndian)
        {
            UInt32 byteOrderMagic = GetByteOrderMagic(currentOffset);
            if (byteOrderMagic == 0x4D3C2B1A)
                swapEndian = true;
            else if (byteOrderMagic == 0x1A2B3C4D)
                swapEndian = false;
            else
                throw new PcapNgParsingErrorException("Unknown BYTE ORDER MAGIC VALUE.");

            // Console.WriteLine(GetVersion(currentOffset, false));

            if (GetVersion(currentOffset, false) != 0x010000)
                throw new PcapNgParsingErrorException("Unsupported file version.");
        }

        #endregion

        #region InterfaceDescriptionBlock

        UInt16 GetInterfaceLinkType(UInt32 offset, bool swapValue)
        {
            BinReader.BaseStream.Seek(offset + 8, SeekOrigin.Begin);
            UInt16 linkType = BinReader.ReadUInt16();
            return swapValue ? Swap(linkType) : linkType;
        }

        UInt32 GetInterfaceSnapLen(UInt32 offset, bool swapValue)
        {
            BinReader.BaseStream.Seek(offset + 12, SeekOrigin.Begin);
            UInt32 snapLen = BinReader.ReadUInt32();
            return swapValue ? Swap(snapLen) : snapLen;
        }

        void ParseInterfaceDescriptionBlock(UInt32 offset, bool swapValue, UInt16 ifaceId, List<PcapNgInterface> sectionIfaceList)
        {
            UInt16 linkType = GetInterfaceLinkType(offset, swapValue);
            UInt32 snapLen = GetInterfaceSnapLen(offset, swapValue);

            PcapNgInterface newIface = new PcapNgInterface(snapLen, linkType, ifaceId);
            sectionIfaceList.Add(newIface);

            UInt32 blockLen = GetGeneralBlockTotalLength(offset, swapValue);

            UInt32 currentOffset = offset + 16;
            UInt32 eofBlockOffset = offset + blockLen - 4;

            // PmConsolePrinter.PrintDebug("Interface description block " + currentOffset + " " + eofBlockOffset);

            //bool eofFound = false;
            while (currentOffset < eofBlockOffset)
            {
                UInt16 optionCode = GetOptionCode(currentOffset, swapValue);
                UInt16 optionLength = GetOptionLength(currentOffset, swapValue);

                UInt32 dataOffset = currentOffset + 4;

                //Console.WriteLine("Option :" + (PcapNgInterface.InterfaceOptions)optionCode + " " + optionLength + " Lenght : "+ optionLength);

                switch ((PcapNgInterface.InterfaceOptions)optionCode)
                {
                    case PcapNgInterface.InterfaceOptions.OptEndofopt:
                        //eofFound = false; 
                        break;
                    case PcapNgInterface.InterfaceOptions.IfName:
                        newIface.IfName = GetOptionString(dataOffset, optionLength); break;
                    case PcapNgInterface.InterfaceOptions.IfDescription:
                        newIface.IfDescription = GetOptionString(dataOffset, optionLength); break;
                    case PcapNgInterface.InterfaceOptions.IfIPv4Addr:
                        newIface.Addresses.Add(new IPAddress(GetOptionBytes(dataOffset, optionLength), 4)); break;
                    case PcapNgInterface.InterfaceOptions.IfIPv6Addr:
                        newIface.Addresses.Add(new IPAddress(GetOptionBytes(dataOffset, optionLength), 4)); break;
                    case PcapNgInterface.InterfaceOptions.IfMaCaddr:
                        newIface.MacAddress = GetOptionBytes(dataOffset, optionLength); break;
                    case PcapNgInterface.InterfaceOptions.IfEuIaddr:
                        newIface.EUIaddr = GetOptionBytes(dataOffset, optionLength); break;
                    case PcapNgInterface.InterfaceOptions.IfSpeed:
                        newIface.Speed = GetOptionUint64(dataOffset, swapValue); break;
                    case PcapNgInterface.InterfaceOptions.IfTsresol:
                        newIface.Tsresol = GetOptionByte(dataOffset);
                        newIface.HasTsresol = true; break;
                    case PcapNgInterface.InterfaceOptions.IfTzone:
                        newIface.Tzone = GetOptionUint64(dataOffset, swapValue); break;
                    case PcapNgInterface.InterfaceOptions.IfFilter:
                        newIface.Filter = GetOptionBytes(dataOffset, optionLength); break;
                    case PcapNgInterface.InterfaceOptions.IfOs:
                        newIface.Os = GetOptionString(dataOffset, optionLength); break;
                    case PcapNgInterface.InterfaceOptions.IfFcslen:
                        newIface.Fcslen = GetOptionByte(dataOffset); break;
                    case PcapNgInterface.InterfaceOptions.IfTsoffset:
                        newIface.Tsoffset = GetOptionUint64(dataOffset, swapValue); break;
                }

                currentOffset += AdjustLength(optionLength) + 4;
            }


        }

        #endregion

        DateTime ConvertUint64ToTimeStamp(UInt64 data, PcapNgInterface iface)
        {
            ulong mult;
            if (iface.HasTsresol)
            {
                byte tsresol = iface.Tsresol;
                int pow = (tsresol & 0x7F);
                if ((tsresol & 0x80) == 0)      // negPower of 10
                    mult = (ulong)Math.Pow(10, pow);
                else  // negPower of 2
                    mult = (ulong)Math.Pow(2, pow);
            }
            else
                mult = 1000000;

            var sec = data / mult;
            var frac = data % mult;
            sec += iface.Tzone;

            const double ticksPerSecond = 10000000;

            var fracMult = ticksPerSecond / mult;

            //  Console.WriteLine("Ticks per second : " + DateTime.TicksPerSecond);


            var pTime = new DateTime(1970, 01, 01, 0, 0, 0);
            pTime = pTime.AddSeconds(sec);
            pTime = pTime.AddTicks((long)(frac * fracMult));
            return pTime;
        }

        #region ParseEnhancedPacketBlock

        UInt32 GetEhcPacketBlockInterfaceID(UInt32 offset, bool swapValue)
        {
            BinReader.BaseStream.Seek(offset + 8, SeekOrigin.Begin);
            var interfaceID = BinReader.ReadUInt32();
            return swapValue ? Swap(interfaceID) : interfaceID;
        }

        UInt32 GetEhcPacketBlockTimestampH(UInt32 offset, bool swapValue)
        {
            BinReader.BaseStream.Seek(offset + 12, SeekOrigin.Begin);
            var value = BinReader.ReadUInt32();
            return swapValue ? Swap(value) : value;
        }

        UInt32 GetEhcPacketBlockTimestampL(UInt32 offset, bool swapValue)
        {
            BinReader.BaseStream.Seek(offset + 16, SeekOrigin.Begin);
            var value = BinReader.ReadUInt32();
            return swapValue ? Swap(value) : value;
        }

        UInt32 GetEhcPacketBlockCapturedLen(UInt32 offset, bool swapValue)
        {
            BinReader.BaseStream.Seek(offset + 20, SeekOrigin.Begin);
            var capturedLen = BinReader.ReadUInt32();
            return swapValue ? Swap(capturedLen) : capturedLen;
        }

        UInt32 GetEhcPacketBlockPacketLen(UInt32 offset, bool swapValue)
        {
            BinReader.BaseStream.Seek(offset + 24, SeekOrigin.Begin);
            var packetLen = BinReader.ReadUInt32();
            return swapValue ? Swap(packetLen) : packetLen;
        }

        enum EnhancedPacketBlockOptions
        {
            EpbFlags = 2,
            EpbHash = 3,
            EpbDropcount = 4
        }

        void ParseEnhancedPacketBlock(UInt32 offset, bool swapValue, List<PcapNgInterface> sectionIfaceList)
        {
            //PmConsolePrinter.PrintDebug("Enhanced Packet Block " + offset);
            UInt32 ifaceID = GetEhcPacketBlockInterfaceID(offset, swapValue);

            if (sectionIfaceList.Count() <= ifaceID || sectionIfaceList[(int)ifaceID].Id != ifaceID)
            {
                throw new PcapNgParsingErrorException("No such an interface !");
            }

            PcapNgInterface iface = sectionIfaceList[(int)ifaceID];

            UInt64 timeStamp = (UInt64)GetEhcPacketBlockTimestampH(offset, swapValue) << 32 |
                               GetEhcPacketBlockTimestampL(offset, swapValue);
            UInt32 capturedLen = GetEhcPacketBlockCapturedLen(offset, swapValue);
            UInt32 packetLen = GetEhcPacketBlockPacketLen(offset, swapValue);

            UInt32 blockLen = GetGeneralBlockTotalLength(offset, swapValue);

            DateTime packetTimeStamp = ConvertUint64ToTimeStamp(timeStamp, iface);

            var newFrame = new PmFramePcapNg(BinReader,
                                             offset,
                                             packetTimeStamp,
                                             convertPcapNgLayer2ToCommonLayer2((PcapNgLinkType)iface.LinkType),
                                             capturedLen,
                                             packetLen,
                                             PmFramePcapNg.FrameBLockType.EnhancedPacket,
                                             iface
                                            );

            UInt32 currentOffset = offset + 28 + AdjustLength(capturedLen);
            UInt32 eofBlockOffset = offset + blockLen - 4;

            //bool eofFound = false;
            while (currentOffset < eofBlockOffset)
            {
                UInt16 optionCode = GetOptionCode(currentOffset, swapValue);
                UInt16 optionLength = GetOptionLength(currentOffset, swapValue);

                UInt32 dataOffset = currentOffset + 4;

                switch ((EnhancedPacketBlockOptions)optionCode)
                {
                    case EnhancedPacketBlockOptions.EpbFlags:
                        newFrame.EpbFlags = GetOptionBytes(dataOffset, 4); break;
                    case EnhancedPacketBlockOptions.EpbHash:
                        newFrame.EpbHash = GetOptionBytes(dataOffset, optionLength); break;
                    case EnhancedPacketBlockOptions.EpbDropcount:
                        newFrame.Dropcount = GetOptionUint64(dataOffset, swapValue); break;
                }
                currentOffset += AdjustLength(optionLength) + 4;
            }
            Frames.Add(newFrame);
        }

        #endregion

        #region ParseSimplyPacketBlock


        UInt32 GetSimplyPacketBlockPacketLen(UInt32 offset, bool swapValue)
        {
            BinReader.BaseStream.Seek(offset + 8, SeekOrigin.Begin);
            var packetLen = BinReader.ReadUInt32();
            return swapValue ? Swap(packetLen) : packetLen;
        }



        void ParseSimplyPacketBlock(UInt32 offset, bool swapValue, List<PcapNgInterface> sectionIfaceList)
        {
            //PmConsolePrinter.PrintDebug("Simply Packet Block " + offset);

            if (!sectionIfaceList.Any())
            {
                throw new PcapNgParsingErrorException("No interface for this packet !");
            }

            UInt32 packetLen = GetSimplyPacketBlockPacketLen(offset, swapValue);

            DateTime packetTimeStamp = new DateTime(0, 0, 0, 0, 0, 0);

            PmFramePcapNg newFrame = new PmFramePcapNg(BinReader,
                                                            offset,
                                                            packetTimeStamp,
                                                            convertPcapNgLayer2ToCommonLayer2((PcapNgLinkType)sectionIfaceList.First().LinkType),
                                                            packetLen,
                                                            packetLen,
                                                            PmFramePcapNg.FrameBLockType.SimplePacket,
                                                            sectionIfaceList.First()
                                                        );
            Frames.Add(newFrame);
        }

        #endregion

        #region ParsePacketBlock(Obsolete)

        UInt16 GetPacketBlockInterfaceID(UInt32 offset, bool swapValue)
        {
            BinReader.BaseStream.Seek(offset + 8, SeekOrigin.Begin);
            var interfaceID = BinReader.ReadUInt16();
            return swapValue ? Swap(interfaceID) : interfaceID;
        }

        UInt16 GetPacketBlockDropsCount(UInt32 offset, bool swapValue)
        {
            BinReader.BaseStream.Seek(offset + 10, SeekOrigin.Begin);
            var interfaceID = BinReader.ReadUInt16();
            return swapValue ? Swap(interfaceID) : interfaceID;
        }

        UInt32 GetPacketBlockTimestampH(UInt32 offset, bool swapValue)
        {
            BinReader.BaseStream.Seek(offset + 12, SeekOrigin.Begin);
            var value = BinReader.ReadUInt32();
            return swapValue ? Swap(value) : value;
        }

        UInt32 GetPacketBlockTimestampL(UInt32 offset, bool swapValue)
        {
            BinReader.BaseStream.Seek(offset + 16, SeekOrigin.Begin);
            var value = BinReader.ReadUInt32();
            return swapValue ? Swap(value) : value;
        }

        UInt32 GetPacketBlockCapturedLen(UInt32 offset, bool swapValue)
        {
            BinReader.BaseStream.Seek(offset + 20, SeekOrigin.Begin);
            var capturedLen = BinReader.ReadUInt32();
            return swapValue ? Swap(capturedLen) : capturedLen;
        }

        UInt32 GetPacketBlockPacketLen(UInt32 offset, bool swapValue)
        {
            BinReader.BaseStream.Seek(offset + 24, SeekOrigin.Begin);
            var packetLen = BinReader.ReadUInt32();
            return swapValue ? Swap(packetLen) : packetLen;
        }

        void ParsePacketBlock(UInt32 offset, bool swapValue, List<PcapNgInterface> sectionIfaceList)
        {
            var interfaceID = GetPacketBlockInterfaceID(offset, swapValue);

            if (sectionIfaceList.Count() <= interfaceID || sectionIfaceList[interfaceID].Id != interfaceID)
            {
                throw new PcapNgParsingErrorException("No such an interface !");
            }

            var iface = sectionIfaceList[interfaceID];
            var dropCount = GetPacketBlockDropsCount(offset, swapValue);

            var timeStamp = (UInt64)GetPacketBlockTimestampH(offset, swapValue) << 32 |
                                       GetPacketBlockTimestampL(offset, swapValue);
            var capturedLen = GetPacketBlockCapturedLen(offset, swapValue);
            var packetLen = GetPacketBlockPacketLen(offset, swapValue);

            var blockLen = GetGeneralBlockTotalLength(offset, swapValue);

            DateTime packetTimeStamp = ConvertUint64ToTimeStamp(timeStamp, iface);
            //DateTime packetTimeStamp = DateTime.Now;

            var newFrame = new PmFramePcapNg(BinReader,
                                                        offset,
                                                        packetTimeStamp,
                                                        convertPcapNgLayer2ToCommonLayer2((PcapNgLinkType)iface.LinkType),
                                                        capturedLen,
                                                        packetLen,
                                                        PmFramePcapNg.FrameBLockType.PacketBlock,
                                                        iface
                                                        );

            Frames.Add(newFrame);

        }

        #endregion

        void ParseFile()
        {
            UInt32 currentOffset = 0;
            uint blockCounter = 0;

            var fileSize = BinReader.BaseStream.Length;
            var swapEndian = false;

            UInt16 ifaceIdCounter = 0;

            List<PcapNgInterface> sectionIfaceList = null;

            var unknownBlocksCount = 0;

            while (currentOffset < fileSize)
            {
                var currentBlockType = RecognizeGeneralBlock(currentOffset, swapEndian);
                blockCounter++;
                /* PmConsolePrinter.PrintDebug("Block type\t: " + currentBlockType);
                 PmConsolePrinter.PrintDebug("Current offset\t: " + currentOffset.ToString());
                 PmConsolePrinter.PrintDebug("Total Length\t: " + (GetNextGeneralBlokOffset(currentOffset, swapEndian) - currentOffset).ToString());
                 PmConsolePrinter.PrintDebug("Block No.\t: " + blockCounter.ToString());
                 PmConsolePrinter.PrintDebugDelimiter();*/

                switch (currentBlockType)
                {
                    case PcapNgBlockType.SectionHeader:
                        ParseSectionHeader(currentOffset, out swapEndian);
                        ifaceIdCounter = 0;
                        sectionIfaceList = new List<PcapNgInterface>();
                        ifaces.Add(sectionIfaceList);
                        break;
                    case PcapNgBlockType.InterfaceDescription:
                        ParseInterfaceDescriptionBlock(currentOffset, swapEndian, ifaceIdCounter, sectionIfaceList);
                        ifaceIdCounter++;
                        break;
                    case PcapNgBlockType.EnhancedPacket:
                        ParseEnhancedPacketBlock(currentOffset, swapEndian, sectionIfaceList);
                        break;
                    case PcapNgBlockType.SimplePacket:
                        ParseSimplyPacketBlock(currentOffset, swapEndian, sectionIfaceList);
                        break;
                    case PcapNgBlockType.PacketBlock:
                        ParsePacketBlock(currentOffset, swapEndian, sectionIfaceList);
                        break;
                }

                UInt32 newOffset = GetNextGeneralBlokOffset(currentOffset, swapEndian);

                if (currentOffset == newOffset)
                {
                    throw new PcapNgParsingErrorException("Broken file ?");
                }

                currentOffset = newOffset;

                if (currentBlockType == PcapNgBlockType.Unknown)
                    unknownBlocksCount++;
            }

            if (unknownBlocksCount > 0)
                throw new PcapNgParsingErrorException(unknownBlocksCount.ToString(CultureInfo.InvariantCulture) + " unknown blocks where skipped ...");
        }

        #endregion

        #region PcapNgOutput

        /// <summary>
        /// Count section header block size without any options (in bytes)
        /// </summary>
        UInt32 CountSectionHeaderBlockSize()
        {
            UInt32 sectionHeaderSize = 0;

            sectionHeaderSize += 4; // Block type
            sectionHeaderSize += 4; // Block total length
            sectionHeaderSize += 4; // Byte Order magic
            sectionHeaderSize += 2; // Major version
            sectionHeaderSize += 2; // Minor- version
            sectionHeaderSize += 8; // Section length
            sectionHeaderSize += 4; // Block total length

            return sectionHeaderSize;
        }

        /// <summary>
        /// Write section header block without any options to output file stream
        /// </summary>
        UInt32 WriteSectionHeaderBlock(BinaryWriter binWriter, UInt64 sectionSize)
        {
            UInt32 blockTotalLength = CountSectionHeaderBlockSize();
            binWriter.Write((UInt32)0x0A0D0D0A);
            binWriter.Write(blockTotalLength);
            binWriter.Write((UInt32)0x1A2B3C4D);
            binWriter.Write(MajorVersion);
            binWriter.Write(MinorVersion);
            binWriter.Write(sectionSize);
            binWriter.Write(blockTotalLength);

            return blockTotalLength;
        }

        /// <summary>
        /// Write section header block without any options to output file stream
        /// Can be used if section size is not known
        /// </summary>
        UInt32 WriteSectionHeaderBlock(BinaryWriter binWriter)
        {
            return WriteSectionHeaderBlock(binWriter, 0xFFFFFFFFFFFFFFFF);
        }

        /// <summary>
        /// Count size of interface description block with one option (tsresol)
        /// </summary>
        UInt32 CountInterfaceDescriptionBlockSize()
        {
            UInt32 interfaceDescriptionBlockSize = 0;

            interfaceDescriptionBlockSize += 4; // Block type
            interfaceDescriptionBlockSize += 4; // Block total length

            interfaceDescriptionBlockSize += 2; // LinkType
            interfaceDescriptionBlockSize += 2; // Reserved

            interfaceDescriptionBlockSize += 4; // SnapLen

            interfaceDescriptionBlockSize += 8; // Option tsresol
            interfaceDescriptionBlockSize += 4; // Option opt_endofopt

            interfaceDescriptionBlockSize += 4; // Block total length

            return interfaceDescriptionBlockSize;
        }

        /// <summary>
        /// Convert DataTime stamp to value in nanoseconds (can be used in output)
        /// </summary>
        UInt64 ConvertTimeStampToUint64(DateTime timeStamp)
        {
            TimeSpan diff = timeStamp - unixDateTimeBase;
            return (UInt64)diff.Ticks * 100;
        }

        /// <summary>
        /// Write interface description block to output file based on specified interface instance
        /// </summary>
        UInt32 WriteInterfaceDescriptionBlock(BinaryWriter binWriter, PcapNgInterface iface)
        {
            var blockTotalLength = CountInterfaceDescriptionBlockSize();
            binWriter.Write((UInt32)0x00000001);        // Block type
            binWriter.Write(blockTotalLength);  // Block total length
            binWriter.Write(iface.LinkType);    // LinkType
            binWriter.Write((UInt16)0x0000);            // reserved
            binWriter.Write(iface.SnapLen);

            // Option tsresol :
            binWriter.Write((UInt16)PcapNgInterface.InterfaceOptions.IfTsresol);
            binWriter.Write((UInt16)0x0001);

            binWriter.Write((byte)0x09);    // in nanoseconds
            binWriter.Write((byte)0x00);    // padding
            binWriter.Write((byte)0x00);    // padding
            binWriter.Write((byte)0x00);    // padding


            // Option opt_endofopt :
            binWriter.Write((UInt32)0x0000);

            binWriter.Write(blockTotalLength);// Block total length

            return blockTotalLength;
        }

        /// <summary>
        /// Count size (in bytes) of Enhanced packet block without packet data and any options
        /// </summary>
        UInt32 CountEnhancedPacketBlockBlockSize()
        {
            UInt32 enhancedPacketBlockBlockSize = 0;

            enhancedPacketBlockBlockSize += 4; // Block type
            enhancedPacketBlockBlockSize += 4; // Block total length
            enhancedPacketBlockBlockSize += 4; // InterfaceID  
            enhancedPacketBlockBlockSize += 4; // Timestamp(High)  
            enhancedPacketBlockBlockSize += 4; // Timestamp(Low)  
            enhancedPacketBlockBlockSize += 4; // CapturedLen  
            enhancedPacketBlockBlockSize += 4; // PacketLen 

            enhancedPacketBlockBlockSize += 4; // Block total length

            return enhancedPacketBlockBlockSize;
        }

        /// <summary>
        /// Write Enhanced pcket block to output file based on specified frame.
        /// Interface will one of the virtual interfaces from vIfaces dictionary
        /// according to frame link type.
        /// </summary>
        UInt32 WriteEnhancedPacketBlock(BinaryWriter binWriter, IPmFrame frame, Dictionary<PmSupportedTypes.LinkTypes, PcapNgInterface> vIfaces)
        {
            byte[] frameData = frame.Data();
            if (frameData == null)
                return 0;

            var enhancedPacketBlockLength = CountEnhancedPacketBlockBlockSize() + (UInt32)frameData.Length;

            PcapNgInterface vIface = vIfaces[frame.LinkType()];

            binWriter.Write((UInt32)0x00000006);
            binWriter.Write(enhancedPacketBlockLength);
            binWriter.Write((UInt32)vIface.Id);

            var timeStampValue = ConvertTimeStampToUint64(frame.TimeStamp());

            binWriter.Write((UInt32)(timeStampValue >> 32));
            binWriter.Write((UInt32)(timeStampValue & 0xFFFFFFFF));

            binWriter.Write(frame.IncludedLength());
            binWriter.Write(frame.OriginalLength());

            binWriter.Write(frameData);

            var paddingLen = CountPadding((UInt32)frameData.Length);

            for (uint i = 0; i < paddingLen; i++)
                binWriter.Write((byte)0x00);

            binWriter.Write(enhancedPacketBlockLength);

            return enhancedPacketBlockLength + paddingLen;
        }


        /// <summary>
        /// Create PCAP-ng file that includes all frames stored in current instace.
        /// For each link type will be created "virtual interface" and all frames 
        /// with this link type will in output file belong to it.
        /// </summary>
        bool CreatePcapNgOutput(String outputFile)
        {
            try
            {
                // holds list of all link types inclede in frame vector
                List<PmSupportedTypes.LinkTypes> framesLinkTypes = new List<PmSupportedTypes.LinkTypes>();

                // TODO - optimize LINQ ?
                foreach (var fr in Frames)
                {
                    if (!framesLinkTypes.Contains(fr.LinkType()))
                        framesLinkTypes.Add(fr.LinkType());
                }

                // holds list of new created virtual interfaces :
                List<PcapNgInterface> virtualInterfaces = new List<PcapNgInterface>();
                // directory for faster lookup :
                var virtualInterfacesDictionary = new Dictionary<PmSupportedTypes.LinkTypes, PcapNgInterface>();

                UInt16 ifaceId = 0;
                foreach (var type in framesLinkTypes)
                {   // create new itnerface for each link type :
                    PcapNgInterface newIface = new PcapNgInterface((UInt16)convertCommonLayer2ToPcapNgLayer2(type), ifaceId);
                    virtualInterfaces.Add(newIface);
                    virtualInterfacesDictionary.Add(type, newIface);
                    ifaceId++;
                }

                // open output file stream :
                var binWriter = new BinaryWriter(File.Open(Path.GetFullPath(outputFile), FileMode.Create));

                // skipt section header for now - will be add at end
                binWriter.BaseStream.Seek(CountSectionHeaderBlockSize(), SeekOrigin.Begin);
                UInt64 sectionSize = virtualInterfaces.Aggregate<PcapNgInterface, ulong>(0, (current, iface) => current + WriteInterfaceDescriptionBlock(binWriter, iface));

                sectionSize = Frames.Aggregate(sectionSize, (current, fr) => current + WriteEnhancedPacketBlock(binWriter, fr, virtualInterfacesDictionary));

                // add section header size (section size is now known) :
                binWriter.BaseStream.Seek(0, SeekOrigin.Begin);
                WriteSectionHeaderBlock(binWriter, sectionSize);

                // close output file
                binWriter.Close();
            }
            /* If anything went bad generate exception and return false */
            catch (Exception ex)
            {
                PmConsolePrinter.PrintError(ex.Message);
                return false;
            }
            /* otherwise return true if everything went good */
            return true;
        }

        #endregion

        #region PcapNgDebugPrints

        String MacAddressToString(byte[] address)
        {
            String result = "";
            if (address != null && address.Length == 6)
            {
                for (int i = 0; i < address.Length; i++)
                {
                    // Display the physical address in hexadecimal.
                    result += address[i].ToString("X2");
                    // Insert a hyphen after each byte, unless we are at the end of the 
                    // address.
                    if (i != address.Length - 1)
                    {
                        result += "-";
                    }
                }
            }

            return result;
        }

        public void PrintInterfacesList()
        {
            PmConsolePrinter.PrintDebug("PCAP-NG INTERFACES FOR FILE " + Path.GetFileName(FilePath));

            var sectionId = 0;
            foreach (var section in ifaces)
            {
                PmConsolePrinter.PrintDebug("SECTION #" + sectionId);
                foreach (var iface in section)
                {
                    PmConsolePrinter.PrintInfoEol("ID> {0}", iface.Id);
                    PmConsolePrinter.PrintInfoEol("SNAPLEN> {0}", iface.SnapLen);
                    PmConsolePrinter.PrintInfoEol("LINKTYPE> {0} \"{1}\"", iface.LinkType, (PcapNgLinkType)iface.LinkType);
                    PmConsolePrinter.PrintInfoEol("IF NAME> \"{0}\"", iface.IfName);
                    PmConsolePrinter.PrintInfoEol("IF DESCRIPTION> \"{0}\"", iface.IfDescription);
                    PmConsolePrinter.PrintInfoEol("ADDRESSES> ");
                    foreach (var address in iface.Addresses)
                        PmConsolePrinter.PrintInfoEol("\t\t" + address.ToString());
                    PmConsolePrinter.PrintInfoEol("MAC ADDRESSES> " + MacAddressToString(iface.MacAddress));
                    PmConsolePrinter.PrintInfoEol("SPEED> {0}", iface.Speed);
                    PmConsolePrinter.PrintInfoEol("OS> \"{0}\"", iface.Os);
                }
                sectionId++;
            }
        }

        #endregion

    }
}
